<?php
namespace plugin\router\src;

/**
 * The MIT License
 *
 * Copyright (c) <year> <copyright holders>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * Classe per la gestione della route contenente i metodi per il settaggio dei parametri
 *
 * @author Damiano Ciarla
 * @package lib
 * @subpackage router
 */
class Route
{
	private $_languagesRoute = array();
	
	private $_defaultRoute = array
	(
		"isStatic"			=> false,
		"route"				=> null,
		"regexp"			=> null,
		"namedParams"		=> null,
		"optionalParams"	=> null,
		"otherParams"		=> array()
	);
	
	/**
	 * Nome della route
	 *
	 * @var string
	 */
	private $_name;

	/**
	 * Parametri di default per controller e action
	 *
	 * @var array
	 */
	private $_defaultParams = array
	(
		'controller'	=> null,
		'action'		=> null
	);

	/**
	 * Parametri aggiuntivi in base al tipo di richiesta HTTP
	 *
	 * @var array
	 */
	private $_methodParams = array
	(
		'GET'		=> array(),
		'POST'		=> array(),
		'PUT'		=> array(),
		'DELETE'	=> array()
	);

	/**
	 * Variabile che se valorizzata, al momento del match della route, se questa
	 * corrisponde all'url da confrontare viene effettuato un redirect 301 verso
	 * l'indirizzo presente nella variabile
	 *
	 * @var string
	 */
	private $_redirect301;

	/**
	 * Regular expression per verificare la presenza di parametri
	 *
	 * @var string
	 */
	private static $_ROUTE_OPTIONAL = "\<[\:|\*|\#]([^\>]+)\>";

	/**
	 * Regular expression per verificare la presenza di parametri opzionali
	 *
	 * @var string
	 */
	private static $_ROUTE_OPTIONAL_PARAMS = "\(([^\<]*)\<[\:|\*|\#]([^\>]+)\>([^\)]*)\)";

	/**
	 * Regular expression per recuperare solo valori alfanumerici
	 *
	 * @var string
	 */
	private static $_REGEX_ALPHANUMERIC = "([a-zA-Z0-9\_\-\+\%\s]+)";

	/**
	 * Regular expression per recuperare solo valori numerici
	 *
	 * @var string
	 */
	private static $_REGEX_NUMERIC = "([0-9]+)";

	/**
	 * Regular expression per recuperare qualsiasi tipo di carattere
	 *
	 * @var string
	 */
	private static $_REGEX_WILDCARD = "(.*)";
	
	/**
	 * Istanza della classe di configurazione del router
	 *
	 * @var RouterConfig
	 */
	private $_routerConfig;

	public function __construct(config\RouterConfig $routerConfig, $name, $route, $controller, $action, array $otherParams)
	{
		$this->_routerConfig = &$routerConfig;
		$this->_name = $name;

		$this->_defaultParams['controller'] = $controller;
		$this->_defaultParams['action'] = $action;

		$this->_settingRoute($route, null, $otherParams);
	}
	
	/**
	 * Metodo che imposta una route per la lingua settata nel primo parametro del metodo
	 *
	 * @param string $lang Lingua
	 * @param string $route Route
	 * 
	 * @return \plugin\router\src\Route 
	 */
	public function addLanguageRoute($lang, $route, array $otherParams = array())
	{
		$this->_settingRoute($route, $lang, $otherParams);
		
		return $this;
	}
	
	private function _settingRoute($route, $lang = null, array $otherParams)
	{
		if (isset($otherParams['controller']))
			unset($otherParams['controller']);

		if (isset($otherParams['action']))
			unset($otherParams['action']);
		
		if($lang !== null)
		{
			if(!count($this->_routerConfig->multilanguagesAccepted) || !in_array($lang, $this->_routerConfig->multilanguagesAccepted))
				throw new exception\RouterException($lang, exception\RouterException::LANG_NOT_FOUND);
			
			$this->_languagesRoute[$lang] = array
			(
				"isStatic"			=> false,
				"route"				=> null,
				"regexp"			=> null,
				"namedParams"		=> null,
				"optionalParams"	=> null,
				"otherParams"		=> ((!count($otherParams) && count($this->_defaultRoute['otherParams'])) ? $this->_defaultRoute['otherParams'] : $otherParams)
			);
		}
		else
		{
			$this->_defaultRoute['otherParams'] = $otherParams;
		}
		
		$routeRegex = null;
		$routeParams = array();
		$routeOptionalParams = array();

		$route = trim($route, '/');

		$routeRegex = $route;

		if(strpos($route, '<') !== false)
		{
			$regexOptionalMatches = array();

			preg_match_all("@" . self::$_ROUTE_OPTIONAL_PARAMS . "@", $route, $regexOptionalMatches, PREG_SET_ORDER);

			if(isset($regexOptionalMatches[0]) && count($regexOptionalMatches) > 0)
			{
				foreach($regexOptionalMatches as $paramMatch)
				{
					$nameOptionalParams = $paramMatch[2];

					if(strpos($paramMatch[2], '|') !== false)
						$nameOptionalParams = substr($paramMatch[2], 0, strpos($paramMatch[2], "|"));

				 	$routeOptionalParams[$nameOptionalParams] = array(
						'routeSegment' => $paramMatch[0],
						'prefix' => $paramMatch[1],
						'suffix' => $paramMatch[3],
					);

					$routeParamToken = substr($paramMatch[0], strlen('(' . $paramMatch[1]));
					$routeParamToken = substr($routeParamToken, 0, -strlen($paramMatch[3] . ')'));

					$routeRegex = str_replace($paramMatch[0], "(?:" . preg_quote($paramMatch[1]) . $routeParamToken . preg_quote($paramMatch[3]) . ")?", $routeRegex);
				}
			}

			$regexMatches = array();

			preg_match_all("@" . self::$_ROUTE_OPTIONAL . "@", $route, $regexMatches, PREG_PATTERN_ORDER);

			if(isset($regexMatches[1]) && count($regexMatches[1]) > 0)
			{
				$routeParamsMatched = array();

				foreach($regexMatches[1] as $paramIndex => $paramName)
				{
					if(strpos($paramName, '|') !== false)
					{
						$routeParamsMatched[] = substr($paramName, 0, strpos($paramName, "|"));
						$routeRegex = str_replace("<:" . $paramName . ">", "(" . substr($paramName, strpos($paramName, "|") + 1) . ")", $routeRegex);
					}
					else
					{
						$routeParamsMatched[] = $paramName;

						$routeRegex = str_replace("<:" . $paramName . ">", self::$_REGEX_ALPHANUMERIC, $routeRegex);
						$routeRegex = str_replace("<#" . $paramName . ">", self::$_REGEX_NUMERIC, $routeRegex);
						$routeRegex = str_replace("<*" . $paramName . ">", self::$_REGEX_WILDCARD, $routeRegex);
					}
				}

				$routeParams = array_combine($routeParamsMatched, $regexMatches[0]);
			}
			else
			{
				if($lang === null)
				{
					$this->_defaultRoute['isStatic'] = true;
				}
				else
				{
					$this->_languagesRoute[$lang]['isStatic'] = true;					
				}
			}

			$routeRegex = str_replace('/', '\/', $routeRegex);
		}
		else
		{
			if($lang === null)
			{
				$this->_defaultRoute['isStatic'] = true;
			}
			else
			{
				$this->_languagesRoute[$lang]['isStatic'] = true;					
			}
		}

		if($lang === null)
		{
			$this->_defaultRoute['route'] = $route;
			$this->_defaultRoute['regexp'] = "/^" . $routeRegex . "$/";
			$this->_defaultRoute['namedParams'] = $routeParams;
			$this->_defaultRoute['optionalParams'] = $routeOptionalParams;
		}
		else
		{
			$this->_languagesRoute[$lang]['route'] = $route;
			$this->_languagesRoute[$lang]['regexp'] = "/^" . $routeRegex . "$/";
			$this->_languagesRoute[$lang]['namedParams'] = $routeParams;
			$this->_languagesRoute[$lang]['optionalParams'] = $routeOptionalParams;
		}
	}

	/**
	 * Metodo che ritorna la lista dei parametri di default della route
	 *
	 * @return array
	 */
	public function defaults()
	{
		return array_merge($this->_defaultParams, $this->_getArrayRouteValue("otherParams"));
	}

	/**
	 * Metodo che restituisce la lista di tutti i parametri presenti nella route
	 *
	 * @return array
	 */
	public function namedParams()
	{
		return $this->_getArrayRouteValue("namedParams");
	}

	/**
	 * Metodo che restituisce la lista dei parametri opzionali
	 *
	 * @return array
	 */
	public function optionalParams()
	{
		return $this->_getArrayRouteValue("optionalParams");
	}

	/**
	 * Restituisce la lista dei parametri opzionali presenti nella route matchati con i
	 * parametri di default
	 *
	 * @return array
	 */
	public function optionalParamDefaults()
	{
		$optionalParamDefaults = array();

		$optionalParams = $this->_getArrayRouteValue("optionalParams");
		
		$defaultParams = $this->defaults();
		
		if($optionalParams && count($optionalParams) > 0)
		{
			foreach($optionalParams as $paramName => $opts)
			{
				if(isset($defaultParams[$paramName]))
				{
					$optionalParamDefaults[$paramName] = $defaultParams[$paramName];
				}
				else
				{
					$optionalParamDefaults[$paramName] = null;
				}
			}
		}

		return $optionalParamDefaults;
	}

	/**
	 * Restituisce la lista dei parametri opzionali con il relativo segment trovato nella route
	 *
	 * @return array
	 */
	public function optionalParamSegment()
	{
		$optionalParamDefaults = array();

		foreach($this->_getArrayRouteValue("optionalParams") as $paramName => $opts)
			$optionalParamDefaults[$paramName] = $opts['routeSegment'];

		return $optionalParamDefaults;
	}

	/**
	 * Metodo che restituisce il valore che descrive se la route è statica o contiene
	 * dei parametri dinamici
	 *
	 * @return boolean
	 */
	public function isStatic()
	{
		return $this->_getArrayRouteValue("isStatic");
	}

	/**
	 * Metodo che restituisce il valore della route
	 *
	 * @return string
	 */
	public function route()
	{
		return $this->_getArrayRouteValue("route");
	}

	/**
	 * Restituisce l'espressione regolare trovata durante il parsing della route
	 * da utilizzare per controllare l'URL che si vuole verificare
	 *
	 * @return string
	 */
	public function regexp()
	{
		return $this->_getArrayRouteValue("regexp");
	}
	
	/**
	 * Metodo che restituisce il valore dell'attributo presente nell'array della
	 * scansione delle varie route
	 *
	 * @param string $name Nome del valore che si vuole recuperare
	 * 
	 * @return mixed
	 */
	private function _getArrayRouteValue($name)
	{
		if(!$this->_routerConfig->isMultilanguages)
		{
			return $this->_defaultRoute[$name];
		}
		else
		{
			$lang = (($this->_routerConfig->currentLang !== null) ? $this->_routerConfig->currentLang : $this->_routerConfig->defaultLang);
			
			if(isset($this->_languagesRoute[$lang]))
			{
				return $this->_languagesRoute[$lang][$name];
			}
			else
			{
				return $this->_defaultRoute[$name];
			}
		}
	}

	/**
	 * Metodo che in base alla presenza del parametro all'interno della funzione
	 * restituisce o imposta il valore per eseguire il redirect 301
	 *
	 * @param string $urlToRedirect
	 *
	 * @return string
	 */
	public function redirect301($urlToRedirect = null)
	{
		if($urlToRedirect === null)
			return $this->_redirect301;

		$this->_redirect301 = $urlToRedirect;
	}

	/**
	 * Metodo che imposta per questa route le alternative per tutte le richieste provenienti
	 * dal protocollo GET
	 *
	 * @param string $controller Nome del controller
	 * @param string $action Nome della action
	 * @param array $otherParam Parametri aggiuntivi da restituire nel caso che il controllo della route abbia esito positivo
	 *
	 * @return Route
	 */
	public function method_GET($controller, $action, array $otherParam = array())
	{
		return $this->_setMethod('GET', $controller, $action, $otherParam);
	}

	/**
	 * Metodo che imposta per questa route le alternative per tutte le richieste provenienti
	 * dal protocollo POST
	 *
	 * @param string $controller Nome del controller
	 * @param string $action Nome della action
	 * @param array $otherParam Parametri aggiuntivi da restituire nel caso che il controllo della route abbia esito positivo
	 *
	 * @return Route
	 */
	public function method_POST($controller, $action, array $otherParam = array())
	{
		return $this->_setMethod('POST', $controller, $action, $otherParam);
	}

	/**
	 * Metodo che imposta per questa route le alternative per tutte le richieste provenienti
	 * dal protocollo PUT
	 *
	 * @param string $controller Nome del controller
	 * @param string $action Nome della action
	 * @param array $otherParam Parametri aggiuntivi da restituire nel caso che il controllo della route abbia esito positivo
	 *
	 * @return Route
	 */
	public function method_PUT($controller, $action, array $otherParam = array())
	{
		return $this->_setMethod('PUT', $controller, $action, $otherParam);
	}

	/**
	 * Metodo che imposta per questa route le alternative per tutte le richieste provenienti
	 * dal protocollo DELETE
	 *
	 * @param string $controller Nome del controller
	 * @param string $action Nome della action
	 * @param array $otherParam Parametri aggiuntivi da restituire nel caso che il controllo della route abbia esito positivo
	 *
	 * @return Route
	 */
	public function method_DELETE($controller, $action, array $otherParam = array())
	{
		return $this->_setMethod('DELETE', $controller, $action, $otherParam);
	}

	/**
	 * Metodo che imposta per questa route le alternative per i diversi tipi di richiesta HTTP
	 *
	 * @param string $method Tipologia della richiesta HTTP
	 * @param string $controller Nome del controller
	 * @param string $action Nome della action
	 * 
	 * @param array $otherParam Parametri aggiuntivi da restituire nel caso che il controllo della route abbia esito positivo
	 *
	 * @return Route
	 */
	private function _setMethod($method, $controller, $action, array $otherParam)
	{
		$this->_methodParams[$method]['controller'] = $controller;
		$this->_methodParams[$method]['action'] = $action;

		if(count($otherParam))
			$this->_methodParams[$method] = array_merge($this->_methodParams[$method], $otherParam);

		return $this;
	}

	/**
	 * Metodo che ritorna i parametri impostati per una determinata richiesta HTTP
	 *
	 * @param string $method Tipologia della richiesta HTTP
	 *
	 * @return array
	 */
	public function getParamsMethod($method)
	{
		return $this->_methodParams[$method];
	}
}
?>